import jwt, { SignOptions } from 'jsonwebtoken';
import User, { IUser, UserStatus } from '@database/models/User';
import config from '@config/index';
import logger from '@utils/logger';

interface LoginCredentials {
  email: string;
  password: string;
}

interface RegisterData {
  username: string;
  email: string;
  password: string;
  fullName: string;
  role?: string;
}

interface TokenPayload {
  userId: string;
  email: string;
  role: string;
}

interface AuthResult {
  user: Partial<IUser>;
  accessToken: string;
  refreshToken: string;
}

class AuthService {
  /**
   * Generate JWT access token
   */
  private generateAccessToken(payload: TokenPayload): string {
    // @ts-ignore - TypeScript strict mode issue with jwt.sign expiresIn type
    return jwt.sign(payload, config.env.JWT_SECRET, {
      expiresIn: config.env.JWT_EXPIRES_IN,
    });
  }

  /**
   * Generate JWT refresh token
   */
  private generateRefreshToken(payload: TokenPayload): string {
    // @ts-ignore - TypeScript strict mode issue with jwt.sign expiresIn type
    return jwt.sign(payload, config.env.JWT_SECRET, {
      expiresIn: config.env.JWT_REFRESH_EXPIRES_IN,
    });
  }

  /**
   * Verify JWT token
   */
  public verifyToken(token: string): TokenPayload {
    try {
      return jwt.verify(token, config.env.JWT_SECRET) as TokenPayload;
    } catch (error) {
      throw new Error('Invalid or expired token');
    }
  }

  /**
   * User login
   */
  async login(credentials: LoginCredentials): Promise<AuthResult> {
    try {
      const { email, password } = credentials;

      // Find user by email OR username (including password field)
      const user = await User.findOne({
        $or: [{ email }, { username: email }],
        status: UserStatus.ACTIVE
      }).select('+password');

      if (!user) {
        throw new Error('Invalid username/email or password');
      }

      // Verify password
      const isPasswordValid = await user.comparePassword(password);
      if (!isPasswordValid) {
        throw new Error('Invalid username/email or password');
      }

      // Update last login time
      user.stats.lastLoginAt = new Date();
      await user.save();

      // Generate tokens
      const payload: TokenPayload = {
        userId: user._id.toString(),
        email: user.email,
        role: user.role,
      };

      const accessToken = this.generateAccessToken(payload);
      const refreshToken = this.generateRefreshToken(payload);

      logger.info(`User ${user.email} logged in successfully`);

      return {
        user: user.toSafeObject(),
        accessToken,
        refreshToken,
      };
    } catch (error) {
      logger.error('Login failed:', error as Error);
      throw error;
    }
  }

  /**
   * User registration
   */
  async register(data: RegisterData): Promise<AuthResult> {
    try {
      const { username, email, password, fullName, role } = data;

      // Check if user already exists
      const existingUser = await User.findOne({
        $or: [{ email }, { username }],
      });

      if (existingUser) {
        if (existingUser.email === email) {
          throw new Error('Email already registered');
        }
        if (existingUser.username === username) {
          throw new Error('Username already taken');
        }
      }

      // Create new user
      const user = new User({
        username,
        email,
        password, // Will be hashed by pre-save hook
        fullName,
        role: role || 'viewer',
        status: UserStatus.ACTIVE,
      });

      await user.save();

      // Generate tokens
      const payload: TokenPayload = {
        userId: user._id.toString(),
        email: user.email,
        role: user.role,
      };

      const accessToken = this.generateAccessToken(payload);
      const refreshToken = this.generateRefreshToken(payload);

      logger.info(`User ${user.email} registered successfully`);

      return {
        user: user.toSafeObject(),
        accessToken,
        refreshToken,
      };
    } catch (error) {
      logger.error('Registration failed:', error as Error);
      throw error;
    }
  }

  /**
   * Refresh access token
   */
  async refreshAccessToken(refreshToken: string): Promise<{ accessToken: string }> {
    try {
      // Verify refresh token
      const payload = this.verifyToken(refreshToken);

      // Check if user still exists and is active
      const user = await User.findOne({
        _id: payload.userId,
        status: UserStatus.ACTIVE,
      });

      if (!user) {
        throw new Error('User not found or inactive');
      }

      // Generate new access token
      const newPayload: TokenPayload = {
        userId: user._id.toString(),
        email: user.email,
        role: user.role,
      };

      const accessToken = this.generateAccessToken(newPayload);

      logger.info(`Access token refreshed for user ${user.email}`);

      return { accessToken };
    } catch (error) {
      logger.error('Token refresh failed:', error as Error);
      throw error;
    }
  }

  /**
   * Get current user info
   */
  async getCurrentUser(userId: string): Promise<Partial<IUser>> {
    try {
      const user = await User.findById(userId);

      if (!user) {
        throw new Error('User not found');
      }

      return user.toSafeObject();
    } catch (error) {
      logger.error('Failed to get current user:', error as Error);
      throw error;
    }
  }

  /**
   * Update user profile
   */
  async updateProfile(
    userId: string,
    updates: Partial<Pick<IUser, 'fullName' | 'avatar' | 'notifications'>>
  ): Promise<Partial<IUser>> {
    try {
      const user = await User.findById(userId);

      if (!user) {
        throw new Error('User not found');
      }

      // Update allowed fields
      if (updates.fullName) user.fullName = updates.fullName;
      if (updates.avatar !== undefined) user.avatar = updates.avatar;
      if (updates.notifications) {
        user.notifications = { ...user.notifications, ...updates.notifications };
      }

      await user.save();

      logger.info(`User ${user.email} profile updated`);

      return user.toSafeObject();
    } catch (error) {
      logger.error('Failed to update profile:', error as Error);
      throw error;
    }
  }

  /**
   * Change password
   */
  async changePassword(
    userId: string,
    oldPassword: string,
    newPassword: string
  ): Promise<void> {
    try {
      const user = await User.findById(userId).select('+password');

      if (!user) {
        throw new Error('User not found');
      }

      // Verify old password
      const isPasswordValid = await user.comparePassword(oldPassword);
      if (!isPasswordValid) {
        throw new Error('Current password is incorrect');
      }

      // Update password
      user.password = newPassword; // Will be hashed by pre-save hook
      await user.save();

      logger.info(`Password changed for user ${user.email}`);
    } catch (error) {
      logger.error('Failed to change password:', error as Error);
      throw error;
    }
  }

  /**
   * Logout (optional: invalidate token in Redis)
   */
  async logout(userId: string): Promise<void> {
    try {
      // TODO: Implement token blacklist in Redis if needed
      logger.info(`User ${userId} logged out`);
    } catch (error) {
      logger.error('Logout failed:', error as Error);
      throw error;
    }
  }
}

export default new AuthService();
